import Dep, { popTarget, pushTarget } from "./dep";

let id = 0;
// 观测数据变化,重新渲染视图
class Watcher {
  // 每一个组件都有一个自己的watcher
  // 为了可以只更新自己,节省性能
  constructor(vm, exprOrFn, options, cb) {
    this.id = id++;
    // 是不是一个渲染watcher
    this.renderWatcher = options;
    // exprOrFn有可能是字符串,要把它变成函数
    if (typeof exprOrFn === 'string') {
      const str = exprOrFn;
      exprOrFn = function () {
        return vm[str];
      }
    }
    // 重新绘制页面的回调
    this.getter = exprOrFn;
    // 当前watcher收集到的dep
    // 后续计算属性和清理工作需要用到
    this.deps = [];
    this.depsId = new Set();
    // 一上来不执行
    this.lazy = options.lazy;
    this.dirty = this.lazy;
    this.vm = vm;
    // 表示用户自己的watch
    this.user = options.user;
    this.cb = cb;
    // 上来先执行一次,this.value是老值,watch用的
    this.value = !this.lazy && this.get();
  }
  addDep(dep) {
    // 一个组件对应多个dep,重复的属性不需要记录
    const id = dep.id;
    // 这里同一个属性无法重复收集
    if (!this.depsId.has(id)) {
      this.deps.push(dep);
      this.depsId.add(id);
      // 让dep也记住watcher
      dep.addSub(this);
    }
  }
  // 提交更新视图
  update() {
    // 如果是计算属性就把dirty变成true
    if (this.lazy) {
      this.dirty = true;
    } else {
      // 多次更新会多次刷新页面,我们希望数据处理完后统一刷新页面
      // 把当前的watcher暂存起来,放到队列中
      queueWatcher(this);
    }
  }
  run() {
    // 再调一次渲染函数即可
    let newValue = this.get();
    let oldValue = this.value;
    if (this.user) {
      // 如果是watch就重新执行cb
      this.cb.call(this.vm, newValue, oldValue);
    }
  }
  // 求值
  evaluate() {
    // 获取用户返回值
    this.value = this.get();
    // 变为不脏
    this.dirty = false;
  }
  // 重新绘制页面
  get() {
    // 把这个watcher绑定到dep上getter触发时会触发_render
    // _render触发时就会取值,就会走到defineProperty的get上
    pushTarget(this);
    const value = this.getter.call(this.vm);
    // 渲染完这个组件再清空这个watcher
    popTarget();
    return value;
  }
  // 让计算属性的watcher记住渲染的watcher
  depend() {
    let i = this.deps.length;
    while (i--) {
      this.deps[i].depend;
    }
  }
}

let callbacks = [];
let waiting = false;
function flushCallBacks() {
  let cbs = callbacks.slice(0);
  waiting = false;
  callbacks = [];
  cbs.forEach(cb => cb());
}
// 降级找nextTick最好的实现方式
let timerFunc;
if (Promise) {
  timerFunc = () => {
    Promise.resolve().then(flushCallBacks);
  }
} else if (MutationObserver) {
  // text里面数据变化就会执行flushCallBacks
  let observe = new MutationObserver(flushCallBacks);
  let textNode = document.createTextNode(1);
  // observe监控textNode
  observe.observe(textNode, {
    // 检测的东西是数据
    characterData: true
  })
  timerFunc = () => {
    textNode.textContent = 2;
  }
} else if (setImmediate) {
  timerFunc = () => {
    setImmediate(flushCallBacks);
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallBacks);
  }
}
export function nextTick(cb) {
  callbacks.push(cb);
  if (!waiting) {
    waiting = true;
    timerFunc();
  }
}


// watcher队列
let queue = [];
// 去重用的
let has = {};
let pending = false;
function queueWatcher(watcher) {
  const id = watcher.id;
  // 去重
  if (!has[id]) {
    queue.push(watcher);
    has[id] = true;
    // 防抖
    if (!pending) {
      pending = true;
      nextTick(flushSchedulerQueue);
    }
  }
}
function flushSchedulerQueue() {
  // 浅拷贝一份,在更新视图时可能有新的watcher进来了,就存一下原来的,再清空
  let flushQuene = queue.slice(0);
  // 清空
  queue = [];
  has = [];
  pending = false;
  // 执行更新视图
  flushQuene.forEach(q => q.run());

}
// 给每一个属性增加一个dep,目的是收集watcher
// 一个组件有：n个属性对应一个组件,就是n个dep对应一个watcher
// 一个属性有可能对应多个组件 1个dep对应多个watcher
// 一个dep会对应多个watcher,一个watcher会对应多个dep
export default Watcher;